﻿
Imports System.Net.Sockets
Imports std
Imports lock_t = lock(Of locks.monitorlock)

Public Class tcpTalk3
    Public Const defaultHoldTimeMs As Int64 = tcpServer3.defaultHoldTimeMs
    Public Const defaultLiveTimeMs As Int64 = tcpServer3.defaultLiveTimeMs
    Private Shared ReadOnly TCPTALK3_REQUEST_QPS As Int64
    Private Shared ReadOnly TCPTALK3_TALK_TIMEMS As Int64
    Private Shared ReadOnly TCPTALK3_CONNECT_TIMEMS As Int64
    Public holdTimeMs As Int64 = 0
    Public liveTimeMs As Int64 = 0
    Private _client As TcpClient = Nothing
    Private _send As nettransData = Nothing
    Private _receive As nettransData = Nothing
    Private lastFinishTicks As Int64 = 0
    Protected connectlock As lock_t
    Protected _connected As ternary = ternary.false

    Public Function senddata() As nettransData
        Return _send
    End Function

    Public Function receivedata() As nettransData
        Return _receive
    End Function

    Protected Function client() As TcpClient
        Return _client
    End Function

    Private Function same_host(ByVal value As String) As Boolean
        Return strsame(_host, value, False)
    End Function

    Private Function same_port(ByVal value As Int32) As Boolean
        Return _port = value
    End Function

    Private _host As String = Nothing
    Public Property host As String
        Get
            Return _host
        End Get
        Set(ByVal value As String)
            If Not same_host(value) Then
                copy(_host, value)
                closeConnection()
            End If
        End Set
    End Property

    Private _port As Int32 = 0
    Public Property port As Int32
        Get
            Return _port
        End Get
        Set(ByVal value As Int32)
            If Not same_port(value) Then
                _port = value
                closeConnection()
            End If
        End Set
    End Property

    Public Function connected() As Boolean
        Return _connected.true_() AndAlso Not tcpTrans.clientClosed(client()) AndAlso Not overlivetime()
    End Function

    Private Function remainingTimeMs(ByVal total As Int64) As Int64
        Return total - ticksToMilliseconds(Now.Ticks() - callbackManager.currentCallbackAction().begin_ticks())
    End Function

    Private Function halfHoldTimeMs() As Int64
        Return holdTimeMs >> 1
    End Function

    Private Function connectHoldTimeMs() As Int64
        Dim rtn As Int64 = 0
        rtn = (holdTimeMs >> 4)
        Const minConnectHoldTimeMs As Int64 = 1000
        Return ifoper(rtn < minConnectHoldTimeMs, minConnectHoldTimeMs, rtn)
    End Function

    Private Function blankTimeMs() As Int64
        Return ticksToMilliseconds(Now.Ticks() - lastFinishTicks)
    End Function

    Private Function overlivetime() As Boolean
        Return blankTimeMs() >= liveTimeMs
    End Function

    Protected Sub updateLastFinishTicks()
        lastFinishTicks = Now.Ticks()
    End Sub

    Private Sub send(ByRef action As callbackAction)
        Dim sendAction As callbackAction = Nothing
        action = New callbackAction(check:=callbackAction.step_check(
                                        Function() As ternary
                                            'check is single thread
                                            If Not connected() Then
                                                connect()
                                                Return callbackAction.step_check_stay
                                            Else
                                                Return callbackAction.step_check_pass
                                            End If
                                        End Function,
                                        Function() As ternary
                                            If tcpTrans3.send(client(), senddata(),
                                                              remainingTimeMs(halfHoldTimeMs()), sendAction) Then
                                                Return callbackAction.step_check_pass
                                            Else
                                                Return callbackAction.step_check_finish
                                            End If
                                        End Function,
                                        callbackAction.action_sub_check(
                                            Function() As callbackAction
                                                Return sendAction
                                            End Function)),
                                    [end]:=Function() As Boolean
                                               If callbackAction.success_finished(sendAction) Then
                                                   Return True
                                               Else
                                                   _connected = ternary.false
                                                   Return False
                                               End If
                                           End Function,
                                    timeoutticks:=millisecondsToTicks(halfHoldTimeMs()))
        assert_begin(action)
    End Sub

    Public Sub talk(Optional ByRef action As callbackAction = Nothing)
        Dim sendAction As callbackAction = Nothing
        Dim receiveAction As callbackAction = Nothing
        counter.increase(TCPTALK3_REQUEST_QPS)
        action = New callbackAction(Function() As Boolean
                                        send(sendAction)
                                        Return True
                                    End Function,
                                    callbackAction.step_check(
                                        callbackAction.action_sub_check(
                                            Function() As callbackAction
                                                Return sendAction
                                            End Function,
                                            Function() As Boolean
                                                Return tcpTrans3.receive(client(),
                                                                         receivedata(),
                                                                         remainingTimeMs(holdTimeMs),
                                                                         action:=receiveAction)
                                            End Function),
                                        callbackAction.action_sub_check(
                                            Function() As callbackAction
                                                Return receiveAction
                                            End Function)),
                                    Function() As Boolean
                                        If callbackAction.success_finished(receiveAction) Then
                                            assert(callbackAction.success_finished(sendAction))
                                            counter.recordTimeMs(TCPTALK3_TALK_TIMEMS,
                                                                 ticksToMilliseconds(sendAction.begin_ticks()))
                                            updateLastFinishTicks()
                                            Return True
                                        Else
                                            _connected = ternary.false
                                            Return False
                                        End If
                                    End Function,
                                    millisecondsToTicks(holdTimeMs))
        assert_begin(action)
    End Sub

    Private Function connect_begin_impl(ByVal a As AsyncCallback,
                                        ByVal obj As Object,
                                        ByVal addresses As vector(Of IPAddress2)) As IAsyncResult
        assert(Not addresses Is Nothing)
        tcpTrans.closeTcpClient(_client)
#If NOT_IN_USE Then
        For i As Int64 = 0 To addresses.size() - 1
            If addresses(i).AddressFamily() = AddressFamily.InterNetworkV6 Then
                Try
                    _client = New TcpClient(AddressFamily.InterNetworkV6)
                    Return client().BeginConnect(addresses(i), port, a, obj)
                Catch
                    tcpTrans.closeTcpClient(_client)
                End Try
            End If
        Next
#End If
        _client = New TcpClient()
        Return client().BeginConnect(+addresses, port, a, obj)
    End Function

    Protected Function connect_begin(ByVal a As AsyncCallback,
                                     ByVal obj As Object,
                                     ByVal addresses As vector(Of IPAddress2)) As IAsyncResult
        Try
            Return connect_begin_impl(a, obj, addresses)
        Catch
            _connected = ternary.false
            Throw
        End Try
    End Function

    'Protected Sub connect_end(ByVal a As IAsyncResult)
    '    Try
    '        client().EndConnect(a)
    '        tcpTrans.initialTcpClient(_client)
    '        _connected = ternary.true
    '        updateLastFinishTicks()
    '    Catch
    '        _connected = ternary.false
    '    End Try
    'End Sub

    Protected Function connect_end(ByVal a As IAsyncResult) As ternary
        Try
            client().EndConnect(a)
            tcpTrans.initialTcpClient(_client)
            updateLastFinishTicks()
            Return ternary.true
        Catch
            Return ternary.false
        End Try
    End Function

    Private Function connect_action(ByVal addresses As vector(Of IPAddress2),
                                    ByVal result As pointer(Of ternary)) As callbackAction
        assert(Not addresses Is Nothing AndAlso addresses.size() > 0)
        assert(result Is Nothing)
        Dim rtn As callbackAction = Nothing
        connectlock.wait()
        If _connected = ternary.false Then
            _connected = ternary.unknown
            Dim startMs As Int64 = 0
            rtn = New callbackActionAsyncOperation(
                          Function(ByRef a As AsyncCallback, ByRef obj As Object) As IAsyncResult
                              startMs = Now.milliseconds()
                              Return connect_begin(a, obj, addresses)
                          End Function,
                          Sub(ByRef a As IAsyncResult)
                              counter.recordTimeMs(TCPTALK3_CONNECT_TIMEMS, startMs)
                              _connected = connect_end(a)
                          End Sub,
                          millisecondsToTicks(connectHoldTimeMs()))
        Else
            rtn = Nothing
        End If
        connectlock.release()
        Return rtn
    End Function

    Private Sub connect(ByRef addresses As vector(Of IPAddress2),
                        ByRef result As pointer(Of ternary),
                        ByRef action As callbackAction)
        action = connect_action(addresses, result)
        If Not action Is Nothing Then
            assert_begin(action)
        End If
    End Sub

    Protected Function connect(Of ASYNC_T)(ByVal con As void(Of vector(Of IPAddress2), pointer(Of ternary), ASYNC_T),
                                           ByVal result As pointer(Of ternary),
                                           Optional ByRef action As ASYNC_T = Nothing) As Boolean
        assert(Not con Is Nothing)
        If host Is Nothing OrElse port = 0 Then
            Return False
        Else
            Dim addresses As vector(Of IPAddress2) = Nothing
            addresses = dnscache.resolve(host)
            If Not addresses Is Nothing AndAlso addresses.size() > 0 Then
                con(addresses, result, action)
                Return True
            Else
                Return False
            End If
        End If
    End Function

    Private Sub connect(Optional ByRef action As callbackAction = Nothing)
        connect(AddressOf connect, CType(Nothing, pointer(Of ternary)), action)
    End Sub

    Protected Sub connect(Of ASYNC_T)(ByVal host As String, ByVal port As Int32,
                                      ByRef action As ASYNC_T,
                                      ByVal con As void(Of vector(Of IPAddress2), pointer(Of ternary), ASYNC_T))
        Dim needReset As Boolean = False
        needReset = Not (same_host(host) AndAlso same_port(port))
        Me._host = host
        Me._port = port
        If needReset Then
            _connected = ternary.false
            connect(con, CType(Nothing, pointer(Of ternary)), action)
        Else
            action = Nothing
        End If
    End Sub

    Public Sub connect(ByVal host As String, ByVal port As Int32, ByRef action As callbackAction)
        connect(host, port, action, AddressOf connect)
    End Sub

    Public Overridable Sub connect(ByVal host As String, ByVal port As Int32)
        connect(host, port, Nothing)
    End Sub

    Private Sub closeConnection()
        tcpTrans.closeTcpClient(_client)
        GC.KeepAlive(_client)
        _connected = ternary.false
    End Sub

    Public Sub reset()
        senddata().clear()
        receivedata().clear()
        closeConnection()
    End Sub

    Shared Sub New()
        TCPTALK3_REQUEST_QPS = counter.registerRateAndLastRate("TCPTALK3_REQUEST_QPS")
        TCPTALK3_TALK_TIMEMS = counter.registerAverageAndLastAverage("TCPTALK3_TALK_TIMEMS")
        TCPTALK3_CONNECT_TIMEMS = counter.registerAverageAndLastAverage("TCPTALK3_CONNECT_TIMEMS")
    End Sub

    Public Sub New()
        _send = New nettransData()
        _receive = New nettransData()
        holdTimeMs = defaultHoldTimeMs
        liveTimeMs = defaultLiveTimeMs
    End Sub

    Protected Overrides Sub Finalize()
        closeConnection()
        MyBase.Finalize()
    End Sub

    Public Overrides Function ToString() As String
        Return displayHostPort(host, port)
    End Function
End Class

Public Class talkXpool_t(Of T As {tcpTalk3, New})
    Inherits pool3(Of T)

    Protected NotOverridable Overrides Function create() As T
        Return New T()
    End Function

    Protected NotOverridable Overrides Sub [erase](ByRef inst As T)
        If Not inst Is Nothing Then
            inst.reset()
        End If
        MyBase.[erase](inst)
    End Sub

    Protected Overrides Sub tie(ByVal inst As T)
        MyBase.tie(inst)
        inst.senddata().clear()
        inst.receivedata().clear()
    End Sub
End Class

Public Class talk3pool_t
    Inherits talkXpool_t(Of tcpTalk3)
End Class

Public Class fixpoint_talkXpool_t(Of T As {tcpTalk3, New})
    Inherits talkXpool_t(Of T)

    Public holdtimeMs As Int64 = 0
    Public livetimeMs As Int64 = 0
    Private host As String = Nothing
    Private port As Int32 = 0

    Public Sub New(ByVal host As String, ByVal port As Int32,
                   Optional ByVal holdtimeMs As Int64 = tcpTalk3.defaultHoldTimeMs,
                   Optional ByVal livetimeMs As Int64 = tcpTalk3.defaultLiveTimeMs)
        MyBase.new()
        copy(Me.host, host)
        copy(Me.port, port)
        copy(Me.holdtimeMs, holdtimeMs)
        copy(Me.livetimeMs, livetimeMs)
    End Sub

    Protected NotOverridable Overrides Sub tie(ByVal inst As T)
        MyBase.tie(inst)
        If Not inst Is Nothing Then
            If inst.connected() Then
                inst.connect(host, port)
                'reconnect to make sure the host & port has not been changed.
            Else
                inst.host = host
                inst.port = port
            End If
            inst.holdTimeMs = holdtimeMs
            inst.liveTimeMs = livetimeMs
        End If
    End Sub
End Class

Public Class fixpoint_talk3pool_t
    Inherits fixpoint_talkXpool_t(Of tcpTalk3)

    Public Sub New(ByVal host As String, ByVal port As Int32,
                   Optional ByVal holdtimeMs As Int64 = tcpTalk3.defaultHoldTimeMs,
                   Optional ByVal livetimeMs As Int64 = tcpTalk3.defaultLiveTimeMs)
        MyBase.new(host, port, holdtimeMs, livetimeMs)
    End Sub
End Class
